package org.semanticflash.rdf.io.rdfxml
{
	
	import com.bigflexlib.utils.AsyncRunner;
	import com.bigflexlib.x.XML.XMLUtil;
	import com.bigflexlib.x.mx.logging.Log.LogUtil;
	
	import mx.logging.ILogger;
	import mx.logging.Log;
	
	import org.semanticflash.rdf.io.AbstractRDFParser;
	import org.semanticflash.rdf.io.IRDFHandler;
	import org.semanticflash.rdf.values.IRI;
	import org.semanticflash.rdf.values.RDFLangLiteral;
	import org.semanticflash.rdf.values.RDFPlainLiteral;
	import org.semanticflash.rdf.values.RDFTypedLiteral;
	import org.semanticflash.rdf.values.RDFValue;
	import org.semanticflash.rdf.vocab.Vrdf;
	

	
	
	/**
	 * 
	 * This class dates back to 2005!
	 * 
	 * 
	 * 
	 * TODO: reified statements, lang, errors
	 * 
	 * 
	 * @author aldo.bucchi@gmail.com
	 * @author aleyton@gmail.com
	 * 
	 */	
	public class RDFXMLParser extends AbstractRDFParser
	{
		
		
		private static const LITERAL			:String = "Literal";
		
		private static const COLLECTION		:String = "Collection";
		
		private static const RESOURCE			:String = "Resource";
		
		
		
		
		
		private var logger:ILogger;
		
		
		
		private var RDF_NS:Namespace = Vrdf.ns;
		
		private var XML_NS:Namespace = new Namespace( "http://www.w3.org/XML/1998/namespace" );
		
		
				
		
		
		
		public function RDFXMLParser( input:*, rdfHandler:IRDFHandler, baseURL:String = null  )
		{

			logger = LogUtil.getStandardLogger( this );

			super( input, rdfHandler, baseURL );
			
						
		}		
		
		
		
		
		
		override protected function start( ):void
		{
		
		
			var rootNode:XML = XML( input ); // we assume a string or XML. this should also accept urlrequests, filestreams, etc
						

			for each ( var node:XML in rootNode.elements( "*" ) )
			{
				AsyncRunner.queue( parseResourceNode, [ node ] );
			}
		
				
		}
		
		
		
		
		
		
		
		
		private function resolveSubject( node:XML ):IRI
		{
			
			// explicit IRI
			if ( node.@RDF_NS::about.length() > 0 )			
				return getIRI( node.@RDF_NS::about.toString(), node   );


			// we can compose the IRI with the base ( of the node ) and the ID
			if ( node.@RDF_NS::ID.length() > 0 )		
				return getIRI( "#" + node.@RDF_NS::ID.toString( ) , node );


			// locally referenced bNode
			if ( node.@RDF_NS::nodeID.length() > 0 )	
				return getBNode( node.@RDF_NS::nodeID.toString() );

 			// blank node with no identifier
			// ( it won't be referenced anywhere else, so we don't need to keep a reference
			return getBNode( );
		
		}
		
		
		
		
		
		
		
		
		private function parseResourceNode( node:XML ):IRI
		{
			
					if ( Log.isDebug() )
						logger.debug( "parseResourceNode: " + node.name( ) );


			var nodeIRI	:IRI 		= getIRI( node );
			var iri			:IRI;

			var uriString	:String;
			


			////////////////////////////////
			// resolve subject
			////////////////////////////////

			var subject:IRI = resolveSubject( node );
			
						
						
						
						
			// check for implicit rdf:type ( whenever the xml node is different from rdf:Description )
			if ( nodeIRI != Vrdf._Description )
			{ 
				assertStatement( subject, Vrdf._type, nodeIRI );
			}
			
			
			
			
			// parse "2.5 Property Attributes" ( plain literals embedded as attributes )
			var attrs:XMLList = node.attributes();
			for each ( var attr:XML in attrs )
			{
				
				var attrIRI:IRI = getIRI( attr );
				
				if ( RDFXMLParserUtil.isConsideredAPropertyAttribute( attrIRI ) )
				{
					assertStatement( 
						subject, 
						attrIRI , 
						RDFPlainLiteral.instance( attr.valueOf( ).toString( ) )
						);						
				}
				
			}


			// handle special types ( Seq, Bag )
			if ( Vrdf.isCollection( nodeIRI ) 	)
			{ // replace rdf:li for rdf:_1, rdf:_2, ...
				RDFXMLParserUtil.enumerateLineElementsWithinContainer( node );											
			}
			
			// parse nested properties
			var propNodes:XMLList = node.elements("*");
			for each ( var propNode:XML in propNodes )
			{
				
				// we make this calls sync. they are usually small
				parsePropertyNode( propNode, subject );
			
			}

			return subject;
			
		}
		
		





		private function parsePropertyNode( node:XML, subject:IRI ):void
		{
			
					if ( Log.isDebug() )
						logger.debug( "parseProperty: " + node.name( ) );


			// predicate
			var predicate:IRI = getIRI( node );

			// parseType
			
			var parseType:String = "default"; // we assign a value so that logger.debug output is consistent
			
			if ( node.@RDF_NS::parseType.length( ) > 0 )
				parseType = node.@RDF_NS::parseType.toString( );


					if ( Log.isDebug() )
						logger.debug( "  parseType: " + parseType );


			switch ( parseType )
			{
				case LITERAL:
					parseProperty_parseTypeLiteral( node, subject, predicate );
				break;
			
				case COLLECTION:
					parseProperty_parseTypeCollection( node, subject, predicate );
				break;
				
				case RESOURCE:
					parseProperty_parseTypeResource( node, subject, predicate );				
				break;
				
				default:
					parseProperty_parseTypeNormal( node, subject, predicate );				
				break;
			}
			
		}




		private function parseProperty_parseTypeNormal( node:XML, subject:IRI, predicate:IRI ):void
		{
			
			

			var lexicalForm:String;

			if ( node.@RDF_NS::ID.length() > 0 )
			{
				
				if ( Log.isDebug() )
					logger.debug( "reified statement: review RDFXMLParser -> parseProperty_parseTypeNormal" );
				
				// uri = fullyQualifyURI( "#" + xmlNode.@RDF_NS::ID.toString() , xmlNode  );
				
			
			}




			if ( node.elements().length() > 0 )
			{ 
															if ( Log.isDebug() ) 
																logger.debug("nested node");
				assertStatement( 
								subject, 
								predicate, 
								parseResourceNode( node.elements()[0] ) 
							);
				return;
			}
			
			
			
			if ( node.@RDF_NS::resource.length() > 0 )
			{
															if ( Log.isDebug() )
																logger.debug("referenced Node through the 'resource' attribute");
				
				assertStatement(
								subject,
								predicate,
								getIRI( node.@RDF_NS::resource.toString( ) , node  )
							);
				
				return;
			}
			
			
			
			
			if ( node.@RDF_NS::nodeID.length() > 0 )
			{
															if ( Log.isDebug() )
																logger.debug( "referenced node through the 'nodeID' attribute");
																	
				assertStatement( 
								subject, 
								predicate, 
								getBNode( node.@RDF_NS::nodeID.toString( ) ) 
							);
				
				return;
			}
			
			
			if ( node.@RDF_NS::datatype.length() > 0 )
			{
				
						if ( Log.isDebug() ) logger.debug( "typed literal" );
				
				
				assertStatement(
								subject, 
								predicate, 
								RDFTypedLiteral.instance(
									node.text( ).toString( ), // lexical form				
									getIRI( node.@RDF_NS::datatype.toString() , node ) // datatype IRI
									)
								);
				return;			
			}
			
					
			
			
			
			// 2.12 Omitting Nodes: Property Attributes on an empty Property Element
			// however, there are two attributes that should not be considered:  ID and resource
			//      we only need to take care of ID, because resource was already parsed a few lines above
			
			var 	numAttributes		:int 		= node.attributes( ).length( ),
					numElements			:int 		= node.elements( ).length( ),
					hasIDAttribute		:Boolean 	= ( node.@RDF_NS::ID.length() > 0 ),
					hasLangAttribute	:Boolean 	= ( node.@lang.length() > 0 );
			
			if (
					( numElements == 0 )
				&&	( !hasLangAttribute )
				&&	( numAttributes > 0 )	
				&&	( ! ( numAttributes==1 && hasIDAttribute )  )
			)
			{
				
				// there is an implicit bNode...
				var bNode:IRI = getBNode( );
				assertStatement( subject, predicate, bNode );
				
				var attrNodes:XMLList = node.attributes( );
				
				for each ( var attrNode:XML in attrNodes )
				{
					var attrIRI:IRI = getIRI( attrNode );
				
					if ( attrIRI != Vrdf._ID ) // we should skip the ID attribute
					{
						assertStatement(
										bNode,
										attrIRI,
										RDFPlainLiteral.instance( attrNode.valueOf( ).toString( ) )
										);						
					}
				}
				return;
			}
			
			
			
			
			if ( 1 == 1 )
			{ // default ( layed out like this for consistency )
			
				var lang:String = hasLangAttribute ? node.@lang : null;
			
				if ( Log.isDebug() ) logger.debug( "literal" );

				if ( node.@RDF_NS::lang.length() > 0 )
					lang = String( node.@RDF_NS::lang ).toLowerCase();
				
				
				lexicalForm = node.text().toString();
				
				
				var obj:RDFValue;
				
				if ( lang != null )
				{
					obj = RDFLangLiteral.instance( lexicalForm, lang );
					if ( lang != "en" )
					{
						return;		
					}
				}
				else
				{
					obj = RDFPlainLiteral.instance( lexicalForm );				
				}
				
				assertStatement( subject, predicate, obj );

				return;			
			}
		
		}
		

		private function parseProperty_parseTypeLiteral( node:XML, subject:IRI, predicate:IRI ):void
		{
			var xmlString:String = "";
			
			var elms:XMLList = node.children( );
			if ( elms.length( ) == 1 )
			{
				xmlString = XML( elms[0] ).toString( );	
			}
			else if ( elms.length() > 1 )
			{
				// yield error
			}
			
			
			if ( Log.isDebug() ) logger.debug( "XMLLiteral with content: " + xmlString );			
			
			assertStatement( subject, predicate , RDFTypedLiteral.instance( xmlString, Vrdf._XMLLiteral ) );
		}


		private function parseProperty_parseTypeCollection( node:XML, subject:IRI, predicate:IRI ):void
		{
			
			var		elms				:XMLList	= node.elements("*"),
					firstChainNode		:IRI 		= getBNode( ),
					currentChainNode	:IRI,
					nextChainNode		:IRI,
					i					:int		= 0,
					l					:int		= elms.length( );


			assertStatement( subject, predicate, firstChainNode );

			
			currentChainNode = firstChainNode;


			for each ( var elm:XML in elms )
			{				
				assertStatement( currentChainNode, Vrdf._first, parseResourceNode( elm ) );
				if ( i == l-1 )
				{ // last round
				
					assertStatement( currentChainNode, Vrdf._rest, Vrdf._nil );
				
				}
				else
				{
					nextChainNode = getBNode( );
					assertStatement( currentChainNode, Vrdf._rest, nextChainNode );
					currentChainNode = nextChainNode;
				}			
				i++;
			}
		}
		


		private function parseProperty_parseTypeResource( node:XML, subject:IRI, predicate:IRI ):void
		{ // 2.11 Omitting Blank Nodes: rdf:parseType="Resource"
		
			var bNode:IRI = getBNode( );
			assertStatement( subject, predicate, bNode );
			
			// parse nested properties
			var props:XMLList = node.elements("*");
			
			for each ( var prop:XML in props )
			{
				parsePropertyNode( prop, bNode );
			}
		}							












		///////////////////////////////////////////////////////////////////////////
		///////////////////////////////////////////////////////////////////////////
		///////////////////////////////////////////////////////////////////////////
		
		
		
		
		/**
		 * 
		 * @param args
		 * 		a string, an XML node or both ( in any order )
		 * 
		 * @return 
		 * 
		 */		
		private function getIRI( ... args ):IRI
		{

			if ( args.length == 1 )
			{
				if ( args[0] is String )
					return IRI.instance( args[0] as String );
				
				if ( args[0] is XML )
					return IRI.instance( XMLUtil.getXMLNodeURI( args[0] as XML ) );				
							
				return null;
			}
			
			
			if ( args.length == 2 )
			{
				
				var str:String;
				
				if ( (args[0] is String) && (args[1] is XML) )			
					str = fullyQualifyURI( args[0] as String, args[1] as XML );
				else if ( (args[1] is String) && (args[0] is XML) )			
					str = fullyQualifyURI( args[1] as String, args[0] as XML ); 
			
				
				if ( str != null )
					return IRI.instance( str );
			
			}

			return null;			
		
		}
		
		


		private function fullyQualifyURI( uri:String, node:XML ):String
		{
			return XMLUtil.fullyQualifyURIWithinXMLNode( uri, node, baseIRI.stringForm );		
		}
		
		
		
		
	}

}